from Compiler import LanguageCompiler
from Helpers import langRe
from Storage import LanguagesStorage

__author__ = 'deadmoroz'

class Highlighter(object):
    def __init__(self):
        self.__compiler = LanguageCompiler()
        self.__storage = LanguagesStorage()

    def __sub_mode(self, lexeme, mode):
        for childMode in mode.contains:
            if childMode.beginRe.match(lexeme):
                return childMode
        return None


    def __end_of_mode(self, mode_index, lexeme):
        mode = self.__modes[mode_index]
        if mode.end and mode.endRe.match(lexeme):
            return 1
        if mode.endsWithParent:
            level = self.__end_of_mode(mode_index - 1, lexeme)
            return level + 1 if level else 0
        return 0


    def __compile_terminators(self, mode, language):
        terminators = []

        for childMode in mode.contains:
            terminators.append(childMode.begin)

        for currentMode in reversed(self.__modes):
            if currentMode.end:
                terminators.append(currentMode.end)
            if not currentMode.endsWithParent:
                break

        if mode.illegal:
            terminators.append(currentMode.illegal)

        return langRe(language, '(' + "|".join(terminators) + ')')


    def __eat_mode_chunk(self, input, language):
        mode = self.__modes[-1]
        startIndex = self.__index
        if not mode.terminators:
            mode.terminators = self.__compile_terminators(mode, language)
        match = mode.terminators.search(input, startIndex)
        if match:
            endIndex = max(match.start(0), startIndex)
            return (input[startIndex: endIndex], match.group(0), False)
        return (input[startIndex:], "", True)

    def __keyword_match(self, language, mode, keyword):
        if language.caseInsensitive:
            keyword = keyword.lower()
        for className in mode.keywordGroups:
            if keyword in mode.keywordGroups[className]:
                return className
        return None

    def __process_buffer(self, language, buffer, mode):
        if mode.subLanguage:
            Highlighter().highlight(mode.subLanguage, buffer, self.__outputBuilder)
        else:
            self.__process_keywords(language, buffer, mode)

    def __start_new_mode(self, mode, lexeme):
        if mode.returnBegin:
            self.__open_mode_if_need(mode)
            mode.buffer = ''
        elif mode.excludeBegin:
            self.__outputBuilder.append_text(lexeme)
            self.__open_mode_if_need(mode)
            mode.buffer = ''
        else:
            self.__open_mode_if_need(mode)
            mode.buffer = lexeme
        self.__modes.append(mode)

    def __process_keywords(self, language, buffer, mode):
        if not mode.keywords:
            self.__outputBuilder.append_text(buffer)
            return
        index = 0
        lexeme = mode.lexemsRe.search(buffer, index) if mode.lexemsRe else None
        while lexeme:
            self.__outputBuilder.append_text(buffer[index: lexeme.start(0)])
            lexemeBody = lexeme.group(0)
            keyword = self.__keyword_match(language, mode, lexemeBody)
            if keyword:
                self.__outputBuilder.append_keyword(keyword, lexemeBody)
            else:
                self.__outputBuilder.append_text(lexemeBody)
            index = lexeme.end(0)
            lexeme = mode.lexemsRe.search(buffer, index)
        self.__outputBuilder.append_text(buffer[index:])


    def __close_mode_if_need(self, mode):
        if mode.className:
            self.__outputBuilder.close_mode()

    def __open_mode_if_need(self, mode):
        if mode.className:
            self.__outputBuilder.open_mode(mode.className)

    def __process_mode_info(self, language, buffer, lexeme, end):
        current_mode = self.__modes[-1]
        if end:
            self.__process_buffer(language, current_mode.buffer + buffer, current_mode)
            return False

        new_mode = self.__sub_mode(lexeme, current_mode)
        if new_mode:
            self.__process_buffer(language, current_mode.buffer + buffer, current_mode)
            self.__start_new_mode(new_mode, lexeme)
            return new_mode.returnBegin

        end_level = self.__end_of_mode(len(self.__modes) - 1, lexeme)
        if end_level:
            if current_mode.returnEnd:
                self.__process_buffer(language, current_mode.buffer + buffer, current_mode)
                self.__close_mode_if_need(current_mode)
            elif current_mode.excludeEnd:
                self.__process_buffer(language, current_mode.buffer + buffer, current_mode)
                self.__close_mode_if_need(current_mode)
                self.__outputBuilder.append_text(lexeme)
            else:
                self.__process_buffer(language, current_mode.buffer + buffer + lexeme, current_mode)
                self.__close_mode_if_need(current_mode)
            while end_level > 1:
                self.__modes.pop()
                self.__close_mode_if_need(self.__modes[-1])
                end_level = end_level - 1

            last_ended_mode = self.__modes.pop()
            self.__modes[-1].buffer = ''
            if last_ended_mode.starts:
                self.__start_new_mode(last_ended_mode.starts, '')
            return current_mode.returnEnd

        if self.__is_illegal(lexeme, current_mode):
            raise Exception('Illegal')

    def __is_illegal(self, lexeme, mode):
        return mode.illegalRe and mode.illegalRe.match(lexeme)


    def highlight(self, languageName, inputBody, outputBuilder):
        language = self.__storage[languageName]
        self.__compiler.compile(language)
        self.__modes = [language.defaultMode]
        self.__index = 0
        self.__outputBuilder = outputBuilder
        language.defaultMode.buffer = ""

        while True:
            (buffer, lexeme, end) = self.__eat_mode_chunk(inputBody, language)
            return_lexeme = self.__process_mode_info(language, buffer, lexeme, end)
            self.__index += len(buffer)
            if not return_lexeme:
                self.__index += len(lexeme)
            if end:
                break

        if len(self.__modes) > 1:
            raise Exception('Illegal')
